use std::ops::Deref;
use std::sync::Arc;
use rbatis::{table_sync, RBatis};
use rbatis::rbdc::DateTime;
use rbatis::table_sync::ColumMapper;
use rbdc_mysql::MysqlDriver;
use reqwest::{Client, Error};
use reqwest::header::{HeaderMap, ACCEPT, ACCEPT_ENCODING, CONTENT_TYPE, HOST, REFERER, USER_AGENT};
use tokio::sync::Mutex;
use crate::model::dto::game_list_model::{Game, GameData, Gameplay, JsonData, RangeTime, RangeYear, SearchOptions, SearchOptionsGames, SearchOptionsLists, SearchOptionsUsers};
use futures::future::{join_all, TryFutureExt};

mod model;

async fn sync_table() {
    fast_log::init(fast_log::Config::new().console()).expect("rbatis init fail");
    let rb = RBatis::new();
    rb.init(MysqlDriver {}, &format!("mysql://root:123456@localhost:13306/game_time"))
        .unwrap();
    let mapper = &table_sync::MysqlTableMapper {} as &dyn ColumMapper;

    RBatis::sync(
        &rb.acquire().await.unwrap(),
        mapper,
        &model::domain::game_domain::Game {
            id: 1,
            name: Some("".to_string()),
            name_date: Some(1),
            alias: Some("".to_string()),
            game_type: Some("".to_string()),
            image: Some("".to_string()),
            comp_lvl_combine: Some(1),
            comp_lvl_sp: Some(1),
            comp_lvl_co: Some(1),
            comp_lvl_mp: Some(1),
            comp_main: Some(1),
            comp_plus: Some(1),
            comp_100: Some(1),
            comp_all: Some(1),
            comp_main_count: Some(1),
            comp_plus_count: Some(1),
            comp_100_count: Some(1),
            comp_all_count: Some(1),
            invested_co: Some(1),
            invested_mp: Some(1),
            invested_co_count: Some(1),
            invested_mp_count: Some(1),
            count_comp: Some(1),
            count_speedrun: Some(1),
            count_backlog: Some(1),
            count_review: Some(1),
            review_score: Some(1),
            count_playing: Some(1),
            count_retired: Some(1),
            profile_popular: Some(1),
            release_world: Some(1),
        },
        "game",
    )
        .await
        .unwrap();
}

async fn fetch_page(page_num: usize, rb: Arc<Mutex<RBatis>>) -> Result<(), Error> {
    let client = Client::new();
    let mut header = HeaderMap::new();
    header.insert(REFERER, "https://howlongtobeat.com/?q=".parse().unwrap());
    header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
    header.insert(ACCEPT_ENCODING, "gzip, deflate, br".parse().unwrap());
    header.insert(USER_AGENT, "Apifox/1.0.0 (https://apifox.com)".parse().unwrap());
    header.insert(ACCEPT, "*/*".parse().unwrap());
    header.insert(HOST, "howlongtobeat.com".parse().unwrap());

    let gameplay = Gameplay {
        perspective: "".to_string(),
        flow: "".to_string(),
        genre: "".to_string(),
    };

    let range_time = RangeTime {
        min: None,
        max: None,
    };

    let range_year = RangeYear {
        min_year: "".to_string(),
        max_year: "".to_string(),
    };

    let search_options_games = SearchOptionsGames {
        user_id: 0,
        platform: "".to_string(),
        sort_category: "popular".to_string(),
        range_category: "main".to_string(),
        range_time,
        gameplay,
        range_year,
        modifier: "".to_string(),
    };

    let search_options_users = SearchOptionsUsers {
        sort_category_users: "postcount".to_string(),
    };

    let search_options_lists = SearchOptionsLists {
        sort_category_lists: "follows".to_string(),
    };

    let search_options = SearchOptions {
        games: search_options_games,
        users: search_options_users,
        lists: search_options_lists,
        filter: "".to_string(),
        sort: 0,
        randomizer: 0,
    };

    let data = JsonData {
        search_type: "games".to_string(),
        search_terms: vec!["".to_string()],
        search_page: page_num as u32,
        size: 25,
        search_options,
        use_cache: true,
    };

    let response = client.post("https://howlongtobeat.com/api/search/bade77bb38d2eecc")
        .headers(header)
        .json(&data)
        .send()
        .await?;

    if (!response.status().is_success()) {
        return Ok(());
    }

    let body = response.json::<GameData>().await?;

    if (body.data.is_none()) {
        return Ok(());
    }

    let mut table_list: Vec<model::domain::game_domain::Game> = vec![];

    for game in body.data.unwrap() {
        let game_domain = model::domain::game_domain::Game {
            id: game.game_id.unwrap() as i64,
            name: game.game_name,
            name_date: game.game_name_date,
            alias: game.game_alias,
            game_type: game.game_type,
            image: game.game_image,
            comp_lvl_combine: game.comp_lvl_combine,
            comp_lvl_sp: game.comp_lvl_sp,
            comp_lvl_co: game.comp_lvl_co,
            comp_lvl_mp: game.comp_lvl_mp,
            comp_main: game.comp_main,
            comp_plus: game.comp_plus,
            comp_100: game.comp_100,
            comp_all: game.comp_all,
            comp_main_count: game.comp_main_count,
            comp_plus_count: game.comp_plus_count,
            comp_100_count: game.comp_100_count,
            comp_all_count: game.comp_all_count,
            invested_co: game.invested_co,
            invested_mp: game.invested_mp,
            invested_co_count: game.invested_co_count,
            invested_mp_count: game.invested_mp_count,
            count_comp: game.count_comp,
            count_speedrun: game.count_speedrun,
            count_backlog: game.count_backlog,
            count_review: game.count_review,
            review_score: game.review_score,
            count_playing: game.count_playing,
            count_retired: game.count_retired,
            profile_popular: game.profile_popular,
            release_world: game.release_world,
        };

        table_list.push(game_domain);
    }

    let rb_lock = rb.lock().await;
    let result = model::domain::game_domain::Game::insert_batch(rb_lock.deref(), &table_list, 25).await.expect("insert into table fail");
    println!("Page {} inserted: {}", page_num, result);

    Ok(())
}

#[tokio::main]
async fn main() {
    // sync_table().await;

    let rb = Arc::new(Mutex::new(RBatis::new()));
    rb.lock().await.init(MysqlDriver {}, &format!("mysql://root:123456@localhost:13306/game_time"))
        .unwrap();

    let num_threads = 24;
    let mut page_num = 0;
    loop {
        let mut futures = vec![];
        for _ in 0..num_threads {
            let rb_clone = Arc::clone(&rb);
            let future = fetch_page(page_num, rb_clone);
            futures.push(future);
            page_num += 1;
        }

        let results = join_all(futures).await;
        for result in results {
            if let Err(_) = result {
                break;
            }
        }
    }
}